home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1997 January: Mac OS SDK / Dev.CD Jan 97 SDK2.toast / Development Kits (Disc 2) / OpenDoc / Developer Documentation / Recipes, Tech Notes & Articles / Recipes / Data Interchange / Data Interchange Basics Part 1 < prev    next >
Encoding:
Text File  |  1996-04-21  |  27.2 KB  |  329 lines  |  [TEXT/ttxt]

  1. OpenDoc™ Recipes
  2.  
  3.  
  4. Data Interchange Basics Part 1
  5. By The OpenDoc Design Team
  6. April, 1996
  7.  
  8. © 1993-1996  Apple Computer, Inc. All Rights Reserved.
  9. Apple, the Apple logo, AppleScript, Bento, Macintosh, QuickTime, and OpenDoc are 
  10. registered trademarks of Apple Computer, Inc.
  11. Finder, Mac, and QuickDraw are trademarks of Apple Computer, Inc. 
  12. SOM, SOMObjects, and System Object Model are licensed trademarks of IBM Corporation. 
  13.  
  14.  
  15. About Data Interchange Basics
  16.  
  17. This document contains recipes for transferring data between OpenDoc™ parts and data interchange objects.   These basic recipes are referenced by the specific recipes for each data interchange object: Clipboard recipes, Drag and Drop recipes, and Linking recipes.   The recipes here assume familiarity with the OpenDoc Storage recipes.  Other OpenDoc recipes that may be of interest to developers implementing data interchange include the Info Recipes.  For descriptions of individual methods used in the recipes, refer to the Class Documentation.
  18.  
  19. Much of this material, except for code examples, has been incorporated into the OpenDoc Programmer's Guide for the Mac OS, published by Addison-Wesley.
  20.  
  21. WARNING: CODE APPEARING IN THESE RECIPES MAY CONTAIN ERRORS.  They are provided to help you get started with data interchange, but need to be customized for your particular content model.
  22.  
  23. New Information for DR5
  24.  
  25. 1) The description of the kODPropCloneKindUsed property recommends that parts always create this property when writing a promise.  Its required when cloning a single embedded frame to a data interchange draft, but is also a simple way to ensure that promises for your own intrinsic data can be fulfilled using the correct clone kind.  See the description of "kODPropCloneKindUsed" under the heading "Data Interchange Properties".
  26.  
  27. 2) Under the heading "Implementing Paste with Link (Embedding Content)", clarified that when pasting a link does not from the clipboard, your part should not call the clipboard's ActionDone method.
  28.  
  29. About The Coding Examples
  30.  
  31. This document contains a number of coding examples in addition to descriptive text.  The coding examples cover:
  32.  
  33. MyCloneStrongReference
  34. MyCloneWeakReference -- How to clone objects referenced by your part.
  35.  
  36. MyWriteToContentSU -- Writing intrinsic content to a data interchange object.
  37.  
  38. MyCloneEmbeddedFrameToContentSU -- Writing a single embedded frame to a data interchange object.
  39.  
  40. CloneInto -- Implementing your part’s CloneInto method.
  41.  
  42. MyReadFromContentSU -- Incorporating content from a data interchange object.
  43.  
  44. MyEmbedContentSU -- Embedding content from a data interchange object.
  45.  
  46. MyMakeEmbeddedFrame -- How your part might make a new frame for embedded content.
  47.  
  48. The coding examples appearing in this document make use of utility routines in addition to public OpenDoc APIs.  The reader is referred to the utilities documentation for information about specific utilities.
  49.  
  50. For simplicity, the coding examples use ODByteArrays to represent the content to be read or written.  If your part does not maintain its content as an ODByteArray, you can either copy it into an ODByteArray, or, more likely, read and write data in chunks using the StorageUnitSetValue or StorageUnitGetValue utility functions.
  51.  
  52. The examples use exception handling in the form of TRY…CATCH_ALL…ENDTRY blocks to catch exceptions, and assumes that errors returned by SOM methods are automatically thrown upon return.
  53.  
  54. To ensure reference-counted objects are released when exceptions are raised, some of these examples employ temporary objects which encapsulate reference-counted objects and automatically release them when their scope is exited.  See the utilities  document "Temporary References/Objects" for more information about temporary objects.
  55.  
  56. Data Interchange Mechanisms
  57.  
  58. The exchange of content within and between parts in OpenDoc involves the use of three mechanisms: the Clipboard, Drag and Drop, and Linking.  While each mechanism has its own characteristics, much of the low-level details are the same.  This document discusses those common details, and provides examples of how your part can be organized to leverage this commonality.
  59.  
  60. The routines shown here are the workhorses of data interchange.  They do the work of moving content to and from a data interchange object.  Once your part has these in place, implementing Clipboard support in you part, for example,  becomes the simpler problem of gaining access at the appropriate time to the clipboard content storage unit.  The same for Drag and Drop and Linking, although with Linking you'll have merge the persistent Linking objects into your content model as well.
  61.  
  62. Content Storage Units
  63.  
  64. At the lowest level, data interchange involves writing content to, and reading content from, one of these data interchange objects: the Clipboard object, the Drag and Drop Object, a Link Source object, or a Link object.  LinkSource and Link objects also happen to be persistent objects.  These four objects have similar interfaces, but these similarities are not based on class inheritance.
  65.  
  66. Each data interchange object has a distinguished storage unit, called the content storage unit , used to exchange content.  Access to this storage unit is provided by a GetContentStorageUnit method defined by all data interchange objects.  The content storage unit serves as the "root" of the storage units that collectively define the content of the interchange object.  Parts can use the same code to write to the content storage unit of any data interchange object.
  67.  
  68. Is important to distinguish between a content storage unit and a persistent object storage unit.  Links and LinkSource objects, for example, are data interchange objects that also happen to be persistent objects.  All persistent objects have storage units which stores their persistent state; Links and LinkSource objects have a storage unit containing their persistent state.  This is NOT the content storage unit, and your part should never read from or write to this storage unit.  Your part must use a content storage unit to exchange data. 
  69.  
  70. Content Storage Units are Part Storage Units
  71.  
  72. Any content storage unit can be considered a part storage unit (although content storage units are never bound to a part editor).  The content storage unit of the clipboard is always a part.   The content storage unit of a Drag and Drop object is always a part.  The content storage unit of a link is always a part.  All content storage units have the only property required for a part: a kODPropContents property.  (Exception: The content storage unit of a link may be missing the kODPropContents property if updating the link failed; see the linking recipes for more information about this situation if your part supports linking.  Likewise, the content storage unit of the Clipboard may also be missing the kODPropContents property if nothing has be cut or copied to the clipboard.  Before cloning a content storage unit from the Clipboard, check to ensure the kODPropContents property is present.)  Parts writing to a content storage unit must ensure they follow the same rules as for any part storage unit, for example, writing all data in the kODPropContents property.  This enables a part to embed a content storage unit by simply cloning it; the destination does not have to “construct” a part storage unit.
  73.  
  74. Data Interchange Properties
  75.  
  76. These properties are relevant to data interchange and may appear in a content storage unit.  Except for the kODPropContents property,  these properties must be read directly from a content storage unit because they are not copied when a content storage unit is cloned.
  77.  
  78. kODPropContents
  79.  
  80. This property contains the data being transferred.  It is in the same format as in a part's storage unit.  Apple recommends that your part write a promise for data interchange, so only data used by a destination is actually transferred.
  81.  
  82. kODPropProxyContent
  83.  
  84. This optional property may appear when a single embedded frame is written to the content storage unit.  This property also contains content, but content that the part initiating the data transfer associates with the embedded frame (like a drop shadow, positional information, or a link source or destination that contains the embedded frame exactly).  Like kODPropContents, this may be promised, too.
  85.  
  86. Proxy content is NOT copied when a destination part clones a content storage unit to embed the content in its draft. Destinations must read proxy content from a content storage unit, not from the storage unit cloned from the content storage unit.
  87.  
  88. kODPropCloneKindUsed
  89.  
  90. This property may be present in a content storage unit to indicate the clone kind to use in fulfilling a promise in the storage unit.  This property must be created by a part cloning a single embedded frame to a data interchange object.  It may also be created by any part writing a promise for intrinsic content to a data interchange object.  This property should contain the value kODCloneCut, kODCloneCopy, or kODCloneToLink.
  91.  
  92. A simple way to ensure the correct clone kind is used when fulfilling a promise is for your part to write this property whenever it writes a promise.  Your FulfillPromise method must already check for this property, and otherwise, your part must encode the clone kind used in the promise data.
  93.  
  94. The clone kind used property is NOT copied when a content storage unit is cloned.
  95.  
  96. kODPropContentFrame
  97.  
  98. This property references a frame storage unit for the data interchange content.  This property should only appear when a single embedded frame is written to the content storage unit.  
  99.  
  100. The content frame property is NOT copied when a destination part clones a content storage unit to embed the content in its draft. Destinations must read proxy content from a content storage unit, not from the storage unit cloned from the content storage unit.
  101.  
  102. kODPropSuggestedFrameShape
  103.  
  104. This property contains a suggested frame shape for use when the content is embedded rather than incorporated at the destination.  This should be written by parts initiating a data transfer that write intrinsic content.  A frame shape should not be written in addition to a kODPropContentFrame property.  The shape applies to all representations in the kODPropContents property.  If neither the kODPropSuggestedFrameShape nor the kODPropContentFrame property exists, the destination part will have to use a default shape for the embedded frame.
  105.  
  106. The frame shape written to the kODPropSuggestedFrameShape property doesn't specify the location of the shape in the original content.  Positional information, in general, is relevant only when the content is pasted into a part of the same category.  In this case, positional information is included as part of the content representation.  If the content is embedded, its location should be determined by the destination part.
  107.  
  108. The suggested frame shape property is NOT copied when a destination part clones a content storage unit to embed the content in its draft. Destinations must read proxy content from a content storage unit, not from the storage unit cloned from the content storage unit.
  109.  
  110. kODPropLinkSpec
  111.  
  112. This optional property is present when a link may be created to the source of the content in the kODPropContents property.   See the linking recipes for  information content linking in OpenDoc and on link specifications.
  113.  
  114. The link specification is NOT copied when a destination part clones a content storage unit to embed the content in its draft. Destinations that wish to create a link must read the link spec from a content storage unit, not from the storage unit cloned from the content storage unit.
  115.  
  116. kODPropMouseDownOffset
  117.  
  118. In a drag and drop operation, the part initiating the drag can add this property to the content storage unit to indicate the offset from the mousedown point to the beginning of the selection, so the part recieving the drop can align the result of the drop with the dragged image, if desired.
  119.  
  120. The mouse down offset is NOT copied when a destination part clones a content storage unit into its draft. 
  121.  
  122. All Data In the Contents Property, Please!
  123.  
  124. All data maintained by your part should be stored in values in the contents property of your storage unit.  Similarly, when write data to a content storage unit, you should only be writing to the contents property.  OpenDoc doesn't expect you to store data in other properties, and such properties are subject to removal.  That said, you're part is free to create as many auxiliary storage units as you like, with whatever properties and kinds you like.  However, you must access these auxiliary storage units from a reference stored in one or more values in your contents  property.
  125.  
  126. Moreover, each value in a contents property should be a self-sufficient representation of your data.  Some values may be lower fidelity representations, such as a plain text representation of a styled text part.  Values may reference common auxiliary storage units, but it should not be necessary to read multiple values to assemble a representation of your data.
  127.  
  128. You are expected to order the values in a contents property in order of decreasing fidelity.  Ordering is determined when the value type is added to the property.  Add or promise the highest fidelity value type first.
  129.  
  130. How Many Kinds are Enough?
  131.  
  132. The number of content kinds your part writes to a content storage unit is entirely up to your part.  In general, your part should write the kind that is the current part kind (not necessarily the highest fidelity kind your part supports), plus one or more standard interchange formats depending on your category.  This is in general a good compromise between size and utility.  Most destinations will be able to accept the data, and you won't be writing too many redundant copies of your data.  Remember that OpenDoc includes a translation facility that destinations can use to provide your data in formats you don't write or don't support, albeit at a potential loss of fidelity.
  133.  
  134. Promise what you have; Deliver what's needed
  135.  
  136. Apple recommends that parts promise content whenever possible when writing to a data interchange object.  That way, only the kinds actually used need to be transferred.  The recipes here demonstrate writing promises.  This also helps clarify the recipes, since the delivery of part-specific content is pushed into your part's FulfillPromise method.
  137.  
  138. When writing a promise in your part's CloneInto method (as described later on in a recipe), a part should determine which of its embedded frames are included in the frame scope, and record those frames as part of the promise data it writes.  The part's FulfillPromise method can then supply the correct content.  Note that a part must fulfill a promise with the content present when the promise was written, even if the original content is changed before the promise is fulfilled.
  139.  
  140. It is up to your part to ensure you can fulfill outstanding promises it has written.  For example, if your part writes promises to the clipboard, you must be able to fulfill those promises for an indefinite period of time.  Note that your part is not informed when promises are removed.  Your part may need to preemptively fulfill clipboard promises should it become impossible for your part to fulfill them in the future.  For example, if your part fulfills promises using undo action data, be aware that your part’s DisposeActionState may be called while a promise still exists on the Clipboard, so your part may need to fulfill the promise at that time.
  141.  
  142. When writing a promise, make sure your FulfillPromise method will have the following information:
  143. •  If the promise is written by your part's CloneInto method, the display frame(s) of your part as identified by the scope parameter.
  144. •  If your part initiated the data transfer, add a kODPropCloneKindUsed property to the storage unit so the correct ODCloneKind constant can be specified when fulling the promise.
  145. •  Some means of identifying the content that must be delivered.
  146.  
  147. Note that your part may write multiple promises for cut content (one promise for each value type).  Your part's FulfillPromise method does not need to behave differently the first time it fulfills a promise for the cut content, verses fulfilling a second promise for the same content.  OpenDoc will ensure that the first time cut data is pasted, the operation is a move, while each subsequent paste behaves like a copy.  Your FulfillPromise can clone using the ODCloneKind value in the kODPropCloneKindUsed property each time.  Any special action required by the cut must be carried out when the promise was written or in the course of handling an Undo action for the cut.
  148.  
  149. Exchanging Intrinsic Vs. Embedded Content
  150.  
  151. Most data transfers involve writing or reading intrinsic content.  Intrinsic content is content managed by the part doing the data transfer; for example, a Text part putting selected text on the clipboard is writing intrinsic content.  If the selection includes an embedded picture as well as text, that too is considered writing intrinsic content.  In both cases, the part initiating the data transfer is writing content to a content storage unit.
  152.  
  153. The one exception to transferring intrinsic content occurs when a single embedded frame is involved.  In this case, the content of primary importance is that in the embedded frame, not in the containing part initiating the transfer.  When a single embedded frame is selected, parts follow different recipes during data interchange.
  154.  
  155. Making the Embed vs. Incorporate Decision at a Destination
  156.  
  157. As long as it has a kODPropContents property, a content storage unit is a part, and can be either incorporated or embedded at a destination.   When a part handles a Paste or Paste As command, or receives a drop, it must decide whether to embed or incorporate the data (simple parts that don't support embedding will just incorporate).
  158.  
  159. If the content storage unit contains a kODPropContentFrame property, the data was cut, copied, or dragged as a single embedded frame.  It should be embedded at the destination, even if the data could be incorporated.
  160.  
  161. Otherwise, on paste or drop the part should incorporate the data if it makes sense according to its content model.  The user can override the part’s default behavior by using the Paste As command.
  162.  
  163. Choosing the Content Kind to Incorporate
  164.  
  165. When incorporating data during a paste or drop, a part must decide which data format to use.  When incorporating content, your part is free to use the highest fidelity kind in the content storage unit that it supports.  It is not necessary to incorporate the preferred kind specified by a kODPropPreferredKind property , if present.
  166.  
  167. Cloning
  168.  
  169. Persistent objects are transferred by a process called cloning.  Your part usually references other persistent objects like embedded frames or link objects.  If your part initiates a transfer of intrinsic content that references other objects, your part transfers these objects by cloning them.  Similarly, your part’s CloneInto method is called when your part is involved in a data transfer operation initiated by another part.  Cloning is described in more detail in the Cloning Overview document.
  170.  
  171. Clone Kinds used to Initiate a Clone Transaction
  172.  
  173. Cloning is performed within a transaction started by calling ODDraft::BeginClone and completed by calling ODDraft::EndClone or ODDraft::AbortClone.  The BeginClone method takes as one of its parameters an ODCloneKind value.  When a part initiates a cloning transaction, its important to specify the clone kind constant appropriate for the semantics of the operation.  This indication is necessary in order for OpenDoc to maintain the behavior defined by the OpenDoc Human Interface Specification when their underlying content is copied or moved.  The value kODCloneCopy informs OpenDoc that the clone is into an intermediate draft (like the clipboard or a drag-and-drop container) with copy semantics; kODCloneCut implies cut semantics. The value kODClonePaste informs OpenDoc that the clone is from an intermediate draft into a destination draft.  Other ODCloneKind values are used when cloning to and from links; see the Linking recipes for more information.
  174.  
  175. Cloning Referenced Objects
  176.  
  177. Your part moves a referenced object to and from a data interchange object by cloning the object.  You clone an object by specifying its object ID; if the object has been internalized, the object will clone itself via its CloneInto method, otherwise, the object's storage unit will perform the clone.
  178.  
  179. Your part distinguishes between strong and weak references.  Strong references are those to objects that must also be cloned.  Weak references are "backward links" that your part uses but which may be broken during data interchange.  Weak references are important because they prevent irrelevant objects from being copied during a clone operation.  When your part clones a strongly or weakly referenced object, its your part's responsibility to use Clone or WeakClone as appropriate.
  180.  
  181. The recipes below show how your part should implement cloning an object from a reference.  Important points to remember:
  182.  
  183. • The methods below must be called during a clone transaction, identified by the draftKey parameter.
  184.  
  185. • The example cloning methods shown below take a parameter specifying the object to be cloned, but do not take a parameter specifying the storage unit to clone into.  Its rare that your part needs to clone into a specific storage unit; the only typical situation is when your part explicitly clones an embedded part into the content storage unit of a data interchange object.  Note that if your part clones an object twice during the same cloning transaction (or when fulfilling a promise) and kODNULLID is specified as the destination storage unit, the same destination will be automatically used the second time.
  186.  
  187. • Never assume a storage unit reference is valid, not even a strong reference.  Test the reference using IsValidStorageUnitRef before calling GetIDFromStorageUnitRef to get the referenced object's ID.
  188.  
  189. • The object ID returned by these methods should not be used to internalize the persistent object until after the clone transaction completes successfully.  Your part must either hold on to the object IDs, or convert them to references.  If your part did not start the clone transaction, you need to test the object ID for validity before creating a reference to it.  If IsValidID returns false, you need to remove the object from the data you're writing, because you won't be able to create the reference.  (GetStrongStorageUnitRef and GetWeakStorageUnitRef fail if called with an invalid object ID.)
  190.  
  191. SOM_Scope ODID  SOMLINK MyPartMyCloneStrongReference(MyPart *somSelf, Environment *ev,
  192.   ODStorageUnit* su,
  193.   ODStorageUnitRef suRef,
  194.   ODDraftKey draftKey)
  195. {
  196.   MyPartData *somThis = MyPartGetData(somSelf);
  197.   MyPartMethodDebug("MyPart","MyCloneStrongReference");
  198.  
  199.   ODID clonedID = kODNULLID;
  200.   
  201.   if ( su->IsValidStorageUnitRef(ev, suRef) )
  202.   {
  203.     SOM_TRY
  204.     
  205.       ODID storageUnitID = su->GetIDFromStorageUnitRef(ev, suRef);
  206.       clonedID = su->GetDraft(ev)->Clone(ev, draftKey, storageUnitID, kODNULLID, kODNULLID);
  207.  
  208.     SOM_CATCH_ALL
  209.     
  210.     SOM_ENDTRY
  211.   }
  212.   
  213.   return clonedID;
  214. }
  215.  
  216.  
  217. SOM_Scope ODID  SOMLINK MyPartMyCloneWeakReference(MyPart *somSelf, Environment *ev,
  218.   ODStorageUnit* su,
  219.   ODStorageUnitRef suRef,
  220.   ODDraftKey draftKey)
  221. {
  222.   MyPartData *somThis = MyPartGetData(somSelf);
  223.   MyPartMethodDebug("MyPart","MyCloneWeakReference");
  224.  
  225.   ODID clonedID = kODNULLID;
  226.   
  227.   if ( su->IsValidStorageUnitRef(ev, suRef) )
  228.   {
  229.     SOM_TRY
  230.     
  231.       ODID storageUnitID = su->GetIDFromStorageUnitRef(ev, suRef);
  232.       clonedID = su->GetDraft(ev)->WeakClone(ev, draftKey, storageUnitID, kODNULLID, kODNULLID);
  233.  
  234.     SOM_CATCH_ALL
  235.     
  236.     SOM_ENDTRY
  237.   }
  238.   
  239.   return clonedID;
  240. }
  241.  
  242. Specifying Platform Kinds
  243.  
  244. If your part supports a standard platform data format, such as 'TEXT' or 'PICT' data on the MacOS, your part can determine the ODValueType designating that kind using the GetISOTypeFromPlatformType method of the translation object.  This example shows how to specify the ODValueType for plain text.  Supporting a platform data type allows your application to interchange data with non-OpenDoc applications supporting that type.
  245.  
  246. ODTranslation* translation = mySession->GetTranslation(ev);
  247. ODValueType isoTypeAppleScrapTEXT = 
  248.     translation->GetISOTypeFromPlatformType(ev, 'TEXT', kODPlatformDataType);
  249.  
  250. Promising Intrinsic Content
  251.  
  252. This example demonstrates a method that can be used to write the content storage unit of a link, the clipboard, or drag and drop object.  This routine can be used only to promise intrinsic data; if a single embedded frame is selected, you should follow the "Writing An Embedded Frame" recipe described below.  
  253.  
  254. When promising intrinsic content, your part typically writes two properties, kODPropContents and kODPropSuggestedFrameShape, and optionally a kODPropPartName.  This is all the content storage unit needs to be either incorporated or embedded at the destination.
  255.  
  256. Your part may write a kODPropPartName property if your content model associates a name with the content being written, and you want that name associated with the part should the content be embedded at the destination.
  257.  
  258. The contentSU argument is the content storage unit of a data interchange object.
  259. The cloneKind parameter specifies the semantics of the operation and is necessary for cloning.
  260. The promiseData will be written into each promised value created by this method in contentSU.
  261. The contentShape parameter will be write into the kODPropSuggestedShape property.
  262. The partName parameter will be written into the kODPropPartName property.
  263.  
  264. Note that as shown here, MyWriteToContentSU:
  265. •  is not a SOM method and may throw an exception rather than returning normally,
  266. • may only be called once to write to a content storage unit; it should not be used, for example, by CloneInto which may be called multiple times and may need to augment the storage unit on sucessive calls.
  267.  
  268. void MyWriteToContentSU (Environment *ev,
  269.   ODStorageUnit* contentSU,
  270.   ODCloneKind cloneKind,
  271.   ODByteArray* promiseData,
  272.   ODShape* contentShape,
  273.   ODIText* partName)
  274. {
  275.   // Begin a clone transaction
  276.   ODDraft* myDraft = fSOMSelf->GetStorageUnit(ev)->GetDraft(ev);
  277.   ODDraftKey draftKey = 0;
  278.  
  279.   ODVolatile(myDraft);
  280.   ODVolatile(draftKey);
  281.  
  282.   draftKey = myDraft->BeginClone(ev, contentSU->GetDraft(ev), kODNULL, cloneKind);
  283.  
  284.   TRY
  285.     contentSU->AddProperty(ev, kODPropContents);
  286.  
  287.     // Write out the contents, preferred representation first.
  288.     //   The type of the best representation is usually the part’s own proprietary format.
  289.     //   Rather that write the actual content, write a promise.
  290.     //   This part will deliver the data in its FulfillPromise method.
  291.  
  292.     contentSU->SetPromiseValue(ev,
  293.         kMyContentKind,
  294.         0,
  295.         promiseData,
  296.         fSOMThis->fPartWrapper);
  297.  
  298.     // Promise standard versions of the content, too.
  299.     //   Note that the very same promise data can be used; FulfillPromise can
  300.     //   get the requested type from its ODStorageUnitView* parameter.
  301.  
  302.     contentSU->SetPromiseValue(ev,
  303.         isoTypeAppleScrapTEXT,
  304.         0,
  305.         promiseData,
  306.         fSOMThis->fPartWrapper);
  307.  
  308.     // Add or replace the name property (for embedding at the destination)
  309.     if ( partName )
  310.       ODSetITextProp(ev, contentSU, kODPropName, kODMacIText, partName);
  311.  
  312.     // Add or replace the suggested frame shape (for embedding at the destination)
  313.     if ( contentShape )
  314.     {
  315.       ODSUForceFocus(ev, contentSU, kODPropSuggestedFrameShape, kODNULL);
  316.       contentShape->WriteShape(ev, contentSU);
  317.     }
  318.  
  319.   CATCH_ALL
  320.  
  321.     myDraft->AbortClone(ev, draftKey);
  322.     RERAISE;
  323.  
  324.   ENDTRY
  325.  
  326.   myDraft->EndClone(ev, draftKey);
  327. }
  328.  
  329. Continued in Part 2...